/**
 * Title: jFamilyTree Text provider
 * Description: jFamilyTree Text provider
 * About: Designed and rewritten by Andreas Lundgren, based on the Treebolic application suite
 * Copyright: (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.provider.text.pair;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import jFamilyTree.model.IEdge;
import jFamilyTree.model.IProvider;
import jFamilyTree.model.IProviderContext;
import jFamilyTree.model.Model;
import jFamilyTree.model.MutableEdge;
import jFamilyTree.model.Settings;
import jFamilyTree.model.Tree;
import jFamilyTree.model.TreeMutableNode;
import jFamilyTree.model.graph.Converter;
import jFamilyTree.model.graph.GraphEdge;
import jFamilyTree.model.graph.GraphNode;
import jFamilyTree.model.graph.MutableGraph;

/**
 * Provider for file system
 * 
 * @author Bernard Bou
 */
public class Provider implements IProvider
{
	static private Color theBackgroundColor = Color.WHITE;

	private class MutableGraphNode extends TreeMutableNode implements GraphNode
	{
		/**
		 * Constructor
		 * 
		 * @param thisId
		 *        id
		 */
		public MutableGraphNode(String thisId)
		{
			super(null, thisId);
		}
	}

	/**
	 * Provider context
	 */
	private IProviderContext theContext;

	/**
	 * Constructor
	 */
	public Provider()
	{
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#set(jFamilyTree.model.IProviderContext)
	 */
	public void connect(IProviderContext thisContext)
	{
		theContext = thisContext;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#makeModel(java.lang.String)
	 */
	public Model makeModel(String thisSource)
	{
		Tree thisTree = makeTree(thisSource, false);
		if (thisTree == null)
			return null;

		// settings
		boolean asTree = thisTree.getRoot().getChildren().size() < 3;
		Settings theseSettings = new Settings();
		theseSettings.theBackColor = theBackgroundColor;
		theseSettings.theNodeBackColor = Color.WHITE;
		theseSettings.theNodeForeColor = Color.BLACK;

		theseSettings.theOrientation = asTree ? "south" : "radial";
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theBackColor = new Color(0xf5f5f0);
		theseSettings.theFontFace = "Dialog";
		theseSettings.theFontSize = 20;
		theseSettings.theFontSizeStep = 2;
		theseSettings.theExpansion = .9;
		theseSettings.theSweep = 1.2;
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theHasStatusbarFlag = true;
		theseSettings.theFocusOnHoverFlag = true;
		theseSettings.theTreeEdgeColor = Color.GRAY;
		theseSettings.theTreeEdgeStyle = IEdge.SOLID | IEdge.TOTRIANGLE | IEdge.TOSOLID;
		return new Model(thisTree, theseSettings);
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#makeTree(java.lang.String, boolean)
	 */
	public Tree makeTree(String thisSource, boolean checkRecursion)
	{
		// get text file
		String thisTextFile = thisSource;
		if (thisTextFile == null)
			thisTextFile = theContext.getParameter("text");

		if (thisTextFile != null)
		{
			// URL
			URL thisURL = theContext.makeURL(thisTextFile);
			if (thisURL != null)
			{
				// parse
				theContext.putProgress("Loading ..." + thisTextFile, false);
				Tree thisTree = parseTree(thisURL);
				if (thisTree != null)
				{
					theContext.putProgress("Loaded " + thisTextFile, false);
					return thisTree;
				}
				else
					theContext.putMessage("Cannot load text file <" + thisTextFile + ">");
			}
			else
				theContext.putMessage("Bad URL " + thisTextFile);
		}
		return null;
	}

	/**
	 * Parse tree
	 * 
	 * @param thisUrl
	 *        text file URL
	 * @return tree
	 */
	private Tree parseTree(URL thisUrl)
	{
		MutableGraph thisGraph = new MutableGraph();
		Map<String, MutableGraphNode> thisMap = new HashMap<String, MutableGraphNode>();
		try
		{
			// input stream
			InputStream thisInputStream = thisUrl.openStream();
			BufferedReader thisReader = new BufferedReader(new InputStreamReader(thisInputStream));

			// parse lines
			String thisLine;
			for (int l = 1; (thisLine = thisReader.readLine()) != null; l++)
			{
				if (thisLine.matches("^\\s*$") || thisLine.startsWith("#"))
					continue;
				processLine(thisLine, l, thisGraph, thisMap);
			}
			return new Converter<MutableGraphNode>().graphToTree(thisGraph);
		}
		catch (Exception ex)
		{
			System.err.println("Text graph parser: " + ex.toString());
			return null;
		}
	}

	/**
	 * Process line
	 * 
	 * @param thisLine
	 *        line
	 * @param thisLineNumber
	 *        line number
	 * @param thisMap
	 *        node stack
	 */
	private void processLine(String thisLine, int thisLineNumber, MutableGraph thisGraph, Map<String, MutableGraphNode> thisMap)
	{
		// parse
		// parent\tchild
		String[] theseFields = thisLine.split("\t");
		if (theseFields.length < 2)
			return;
		String thisParentId = theseFields[0].trim();
		if (thisParentId == null || thisParentId.isEmpty())
			System.err.println("ERR " + thisLineNumber);
		String thisChildId = theseFields[1].trim();
		if (thisChildId == null || thisChildId.isEmpty())
			System.err.println("ERR " + thisLineNumber);

		// nodes
		MutableGraphNode thisParent = thisMap.get(thisParentId);
		if (thisParent == null)
		{
			thisParent = new MutableGraphNode(thisParentId);
			thisParent.setLabel(thisParentId);
			thisMap.put(thisParentId, thisParent);
			thisGraph.add(thisParent);
		}
		MutableGraphNode thisChild = thisMap.get(thisChildId);
		if (thisChild == null)
		{
			thisChild = new MutableGraphNode(thisChildId);
			thisChild.setLabel(thisChildId);
			thisMap.put(thisChildId, thisChild);
			thisGraph.add(thisChild);
		}

		// edge
		GraphEdge thisGraphEdge = new GraphEdge(thisParent, thisChild, true);
		thisGraphEdge.setUserData(new MutableEdge(thisParent, thisChild));
		thisGraph.add(thisGraphEdge);
	}
}
